#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Minimal web interface to cve-search to display the last entries
# and view a specific CVE.
#
# Software is free software released under the "GNU Affero General Public License v3.0"
#
# Copyright (c) 2013-2018  Alexandre Dulaunoy - a@foo.be
# Copyright (c) 2014-2018  Pieter-Jan Moreels - pieterjan.moreels@gmail.com

# imports
import os
import re
import sys
import urllib
from collections import defaultdict

from flask import render_template, request

_runPath = os.path.dirname(os.path.realpath(__file__))
sys.path.append(os.path.join(_runPath, ".."))

from lib.Toolkit import isURL
from lib.DatabaseLayer import getCVEs, getCAPECFor, getCAPEC, cvesForCPE, getSearchResults, via4Linked
from lib.Config import Configuration
from web.api import API, APIError


class Minimal(API):
    #############
    # Variables #
    #############
    defaultFilters = {
        "timeSelect": "all",
        "startDate": "",
        "endDate": "",
        "timeTypeSelect": "Modified",
        "cvssSelect": "all",
        "cvss": "",
        "rejectedSelect": "hide",
    }
    args = {
        "pageLength": Configuration.getPageLength(),
        "listLogin": Configuration.listLoginRequired(),
        "minimal": True,
    }

    def __init__(self):
        self.minimal = True
        super().__init__()
        routes = [
            {"r": "/", "m": ["GET"], "f": self.index},
            {"r": "/", "m": ["POST"], "f": self.index_post},
            {"r": "/r/<int:r>", "m": ["GET"], "f": self.index_filter_get},
            {"r": "/r/<int:r>", "m": ["POST"], "f": self.index_filter_post},
            {"r": "/cve/<cveid>", "m": ["GET"], "f": self.cve},
            {"r": "/cwe", "m": ["GET"], "f": self.cwe},
            {"r": "/cwe/<cweid>", "m": ["GET"], "f": self.relatedCWE},
            {"r": "/capec/<capecid>", "m": ["GET"], "f": self.capec},
            {"r": "/browse", "m": ["GET"], "f": self.browse},
            {"r": "/browse/", "m": ["GET"], "f": self.browse},
            {"r": "/browse/<vendor>", "m": ["GET"], "f": self.browse},
            {"r": "/search/<vendor>/<path:product>", "m": ["GET"], "f": self.search},
            {"r": "/search", "m": ["POST"], "f": self.freetext_search},
            {"r": "/link/<key>/<value>", "m": ["GET"], "f": self.link},
        ]
        filters = [
            {"n": "htmlEncode", "f": self.htmlEncode},
            {"n": "htmlDecode", "f": self.htmlDecode},
            {"n": "sortIntLikeStr", "f": self.sortIntLikeStr},
        ]
        context_processors = [self.JSON2HTMLTable]
        error_handlers = [{"e": 404, "f": self.page_not_found}]

        for route in routes:
            self.addRoute(route)
        for _filter in filters:
            self.addFilter(_filter)
        for context in context_processors:
            self.addContextProcessors(context)
        for handler in error_handlers:
            self.app.register_error_handler(handler["e"], handler["f"])

    #############
    # Functions #
    #############
    def addFilter(self, _filter):
        self.app.add_template_filter(_filter["f"], _filter["n"])

    def addContextProcessors(self, context_processor):
        self.app.context_processor(context_processor)

    def getFilterSettingsFromPost(self, r):
        filters = dict(request.form)
        errors = False
        # retrieving data
        try:
            cve = self.filter_logic(filters, r)
        except Exception as e:
            cve = getCVEs(limit=self.args["pageLength"], skip=r)
            errors = True
        return {"filters": filters, "cve": cve, "errors": errors}

    ##########
    # ROUTES #
    ##########
    # /
    def index(self):
        cve = self.filter_logic(self.defaultFilters, 0)
        return render_template("index.html", cve=cve, r=0, **self.args)

    # /
    def index_post(self):
        args = dict(self.getFilterSettingsFromPost(0), **self.args)
        return render_template("index.html", r=0, **args)

    # /r/<r>
    def index_filter_get(self, r):
        if not r or r < 0:
            r = 0
        cve = self.filter_logic(self.defaultFilters, r)
        return render_template("index.html", cve=cve, r=r, **self.args)

    # /r/<r>
    def index_filter_post(self, r):
        if not r or r < 0:
            r = 0
        args = dict(self.getFilterSettingsFromPost(r), **self.args)
        return render_template("index.html", r=r, **args)

    # /cve/<cveid>
    def cve(self, cveid):
        cve = self.api_cve(cveid)
        if not cve:
            return render_template(
                "error.html",
                status={"except": "cve-not-found", "info": {"cve": cveid}},
                minimal=self.minimal,
            )
        return render_template("cve.html", cve=cve, minimal=self.minimal)

    # /cwe
    def cwe(self):
        cwes = [x for x in self.api_cwe() if x["weaknessabs"].lower() == "class"]
        return render_template("cwe.html", cwes=cwes, capec=None, minimal=self.minimal)

    # /cwe/<cweid>
    def relatedCWE(self, cweid):
        cwes = {x["id"]: x["name"] for x in self.api_cwe()}
        return render_template(
            "cwe.html",
            cwes=cwes,
            cwe=cweid,
            capec=getCAPECFor(cweid),
            minimal=self.minimal,
        )

    # /capec/<capecid>
    def capec(self, capecid):
        cwes = {x["id"]: x["name"] for x in self.api_cwe()}

        req_capec = getCAPEC(capecid)

        rel_capecs = defaultdict(dict)

        if len(req_capec["related_capecs"]) != 0:
            for each in req_capec["related_capecs"]:
                rel_capecs[each] = getCAPEC(each)["summary"]

        return render_template(
            "capec.html", cwes=cwes, capecs=dict(rel_capecs), capec=req_capec, minimal=self.minimal
        )

    # /browse
    # /browse/
    # /browse/<vendor>
    def browse(self, vendor=None):
        try:
            data = self.api_browse(vendor)
            if "product" in data and "vendor" in data:
                return render_template(
                    "browse.html",
                    product=data["product"],
                    vendor=data["vendor"],
                    minimal=self.minimal,
                )
            else:
                return render_template(
                    "error.html",
                    minimal=self.minimal,
                    status={"except": "browse_exception", "info": "No CPE"},
                )
        except APIError as e:
            return render_template(
                "error.html",
                minimal=self.minimal,
                status={"except": "browse_exception", "info": e.message},
            )

    # /search/<vendor>/<product>
    def search(self, vendor=None, product=None):
        search = vendor + ":" + product
        cve = cvesForCPE(search)
        return render_template(
            "search.html", vendor=vendor, product=product, cve=cve, minimal=self.minimal
        )

    # /search
    def freetext_search(self):
        search = request.form.get("search")
        if search == "":
            return self.index()
        result = getSearchResults(search)
        cve = {"results": result["data"], "total": len(result["data"])}
        errors = result["errors"] if "errors" in result else []
        return render_template(
            "search.html",
            cve=cve,
            errors=errors,
            freetextsearch=search,
            minimal=self.minimal,
        )

    # /link/<key>/<value>
    def link(self, key=None, value=None):
        key = self.htmlDecode(key)
        value = self.htmlDecode(value)
        regex = re.compile(re.escape(value), re.I)
        cve = via4Linked(key, regex)
        cvssList = [float(x["cvss"]) for x in cve["results"] if x.get("cvss")]
        if cvssList:
            stats = {
                "maxCVSS": max(cvssList),
                "minCVSS": min(cvssList),
                "count": len(cve),
            }
        else:
            stats = {"maxCVSS": 0, "minCVSS": 0, "count": len(cve)}
        return render_template(
            "linked.html",
            via4map=key.split(".")[0],
            field=".".join(key.split(".")[1:]),
            value=value,
            cve=cve,
            stats=stats,
            minimal=self.minimal,
        )

    ###########
    # Filters #
    ###########
    def htmlEncode(self, string):
        return urllib.parse.quote_plus(string).lower()

    def htmlDecode(self, string):
        return urllib.parse.unquote_plus(string)

    def sortIntLikeStr(self, datalist):
        return sorted(datalist, key=lambda k: int(k))

    def JSON2HTMLTable(self):
        # Doublequote, because we have to |safe the content for the tags
        def doublequote(data):
            return urllib.parse.quote_plus(urllib.parse.quote_plus(data))

        def JSON2HTMLTableFilter(data, stack=None):
            _return = ""
            if type(stack) == str:
                stack = [stack]

            if type(data) == list:
                if len(data) == 1:
                    _return += JSON2HTMLTableFilter(data[0], stack)
                else:
                    _return += '<ul class="via4">'
                    for item in data:
                        _return += "<li>%s</li>" % JSON2HTMLTableFilter(item, stack)
                    _return += "</ul>"
            elif type(data) == dict:
                _return += '<table class="invisiTable">'
                for key, val in sorted(data.items()):
                    _return += "<tr><td><b>%s</b></td><td>%s</td></tr>" % (
                        key,
                        JSON2HTMLTableFilter(val, stack + [key]),
                    )
                _return += "</table>"
            elif type(data) == str:
                if stack:
                    _return += (
                        "<a href='/link/"
                        + doublequote(".".join(stack))
                        + "/"
                        + doublequote(data)
                        + "'>"
                    )  # link opening
                    _return += "<span class='glyphicon glyphicon-link' aria-hidden='true'></span> </a>"
                _return += (
                    "<a target='_blank' href='%s'>%s</a>" % (data, data)
                    if isURL(data)
                    else data
                )
            _return += ""
            return _return

        return dict(JSON2HTMLTable=JSON2HTMLTableFilter)

    ##################
    # Error Messages #
    ##################
    def page_not_found(self, e):
        return render_template("404.html", minimal=self.minimal), 404


if __name__ == "__main__":
    server = Minimal()
    server.start()
